module(..., package.seeall)
require'zdc.idutil'
require'zdc.typeex'
require'zdc.actmag'
local GetTypeMeta=zdc.typeex.GetTypeMeta
local _defaulttype=zdc.typeex.NOTYPE
local convval=require'zdc.sandbox'.convval
function loadactfun(dal,n,f)
	local am=n.__dpl.actmag or dal.actmag or zdc.actmag.DEFAULT
	return am:RequireActFun(f)
end
function getnodeact(dal,n,key)
	local act=n:__get_act_define(key)
	local actfun=act and act.act
	if actfun then
		if type(actfun)~='function' then
			actfun=assert(loadactfun(dal,n,actfun))
		end
		return actfun
	else
		return nil
	end
end
local function predefcall(dal,n,a,...)
	local ta=type(a)
	if ta=='function' or ta=='table' then return a(...) end
	if not a then return nil,'not support' end
	a=assert(loadactfun(dal,n,a))
	return a(...)
end
local function _hookchg(user,dal,_fd,n,k,nv,org,lvl,indel)
	local hka,hkta
	local hkb,hkt=_fd and _fd.hkb,n._hkt
	if hkb then
		r,nv,hka=hkb(user,dal,n,k,nv,org,lvl,indel)
		if not r then return r,nv or 'EDBH' end
	end
	hkb=hkt and hkt.hkb
	if hkb then
		r,nv,hkta=hkb(user,dal,n,k,nv,org,lvl,indel)
		if not r then return r,nv or 'EDBBH' end
	end

	n:__set_field(_fd,k,nv)
	if hka then hka(user,dal,n,k,nv,org,lvl,indel) end
	hka=_fd and _fd.hka
	if hka then hka(user,dal,n,k,nv,org,lvl,indel) end
	if hkta then hkta(user,dal,n,k,nv,org,lvl,indel) end
	hka=hkt and hkt.hka
	if hka then hka(user,dal,n,k,nv,org,lvl,indel) end
	return true
end
---------------------------------------------------
local function set_sub(user,dal,_fd,n,k,nv,org,lvl,indel)
	-- check the custom set on field
	if _fd.set~=nil then
		return false,'EDBS1H:set'
	end
	return _hookchg(user,dal,_fd,n,k,nv,org,lvl,indel)
end
function set(user,dal,n,k,v,lvl)
	if user and not n:__check_acl(user,'_set') then
		return nil,'ENPF1:' .. n.__id .. ':_set'
	end
	local _fd=n:__get_field_define(k)
	if not _fd then return nil,'ENTDF1:'.. n.__id .. '.' .. k end
	if user and _fd.ro then
		return nil, 'EDCFRF1:'.. n.__id .. '.' .. k
	end
	local ft=_fd[2] or _defaulttype
	local r,nv=ft:_validU(v,_fd[3])
	if not r then return r,'err when set '..k..':'..nv end
	
	local err
	-- check the custom create on field
	if _fd.set~=nil then 
		r,err=predefcall(dal,n,_fd.set,user,dal,n,k,nv,lvl)
	else
		r,err=_hookchg(user,dal,_fd,n,k,nv,n[k],lvl)
	end
	if r then n:__save_ta() end
	return r,err
end

function mutilset(user,dal,n,args,lvl1,lvl2,lvl)
	if user and not n:__check_acl(user,'_set') then
		return nil,'ENPF1:' .. n.__id .. ':_set'
	end
	local of,fd,er=n:__get_ordered_fields(args)
	
	if args._pto or args._fds or args._dfs then
		fd=nil
		for k,v in ipairs(er) do
			table.insert(of,v)
		end
		er={}
	end

	
	lvl1=lvl1 or 1
	local ok={}
	for _,k in ipairs(of) do
		v=args[k]
		local fds=(fd and fd[k]) or n:__get_field_define(k)
		l1=fds and fds[1]
		if l1 and ((l1>=lvl1) and (not lvl2 or l1<lvl2)) then
			local ft=fds[2] or _defaulttype
			local r,nv=ft:_validU(v,fds[3])
			if not r then
				return r,nv
			end
			r,nv=set_sub(user,dal,fds,n,k,nv,n[k],lvl)
			if not r then
				return r,nv
			end
			table.insert(ok,k)
		else
			table.insert(er,k)
		end
	end
	if #ok>0 then n:__save_ta() end
	--print('nofield define for ',table.concat(er,','))
	return true,ok,er
end
function get(user,dal,n,k,conv)
	if user and not n:__check_acl(user,'_get') then
		return nil,'ENPF1:' .. n.__id .. ':_get'
	end
	local _fd=n:__get_field_define(k)
	if not _fd then return nil,'ENTDF1:'.. n.__id .. '.' .. k end
	local v = n[k]
	if conv then
		local ft=_fd[2]
		if ft and ft._showS then return ft:_showS(v) end
	end
	return v
end

local function _unsethookchg(user,dal,_fd,n,k,org,nv,lvl)
	local hka,hkta
	local hkb,hkt=_fd and _fd.hkb,n._hkt
	if hkb then
		r,nv,hka=hkb(user,dal,n,k,nv,org,lvl)
		if not r then return r,nv or 'EDBBH' end
	end
	hkb=hkt and hkt.hkb
	if hkb then
		r,nv,hkta=hkb(user,dal,n,k,nv,org,lvl)
		if not r then return r,nv or 'EDBBH' end
	end

	n:__set_field(_fd,k,nil)
	
	if hka then hka(user,dal,n,k,nv,org,lvl) end
	hka=_fd and _fd.hka
	if hka then hka(user,dal,n,k,nv,org,lvl) end
	if hkta then hkta(user,dal,n,k,nv,org,lvl) end
	hka=hkt and hkt.hka
	if hka then hka(user,dal,n,k,nv,org,lvl) end
	return true
end
function unset(user,dal,n,k,lvl)
	if user and not n:__check_acl(user,'_set') then
		return nil,'ENPF1:' .. n.__id .. ':_set'
	end
	local _fd=n:__get_field_define(k)
	if not _fd then return nil, 'ENTDF1:'..k end
	if user and _fd.ro then
		return nil, 'EDUFRF1:'.. n.__id ..'.' ..k
	end
	local r=n:__is_proto_val(k)
	if r then return true end
	local a=n:__get_pto_field(k,fd)
	if a==nil and n._fds[k] then return nil,'E1NBU:'..n.__id .. '.' ..k end
	local err
	-- check the custom create on field
	if _fd.set~=nil then
		r,err=predefcall(dal,n,_fd.set,user,dal,n,k,nil,lvl)
	else
		r,err=_unsethookchg(user,dal,_fd,n,k,n[k],a,lvl)
	end
	if r then n:__save_ta() end
	return r,err
end

function seek(root,path)
	local cn,err = root
	for s in string.gmatch(path,'[^/]+') do
		cn,err = cn:__get_child(s)
		if not cn then return nil,err end
	end
	return cn
end

-- cb(false,errmsg)
-- cb(true,...)
function asynccall(user,dal,cb,n,actname,...)
	local act=n:__get_act_define(actname)
	local actfun=act and act.act
	if actfun then
		if user and not n:__check_acl(user,actname) then
			return nil,'ENPF1:'..n.__id .. ':' .. actname
		end
		if type(actfun)~='function' then
			actfun=assert(loadactfun(dal,n,actfun))
		end
		if act.async then
			local r,msg=actfun(cb,user,dal,n,actname,...)
			if not r or r<0 then
				if cb then cb(r,msg) end
				return true
			else
				return true,'background running...'
			end
		else
			if cb then cb(0,actfun(user,dal,n,act,...)) end
			return true
		end
	end
	return false,'ENADF1:'..n.__id .. ':' .. actname
end
function asynccallactpath(user,dal,cb,actpath,...)
	local node, fun = string.match(actpath, "^(.+)%.(.-)$")
	if not node or not fun then return false,'EBCP1:'..actpath end
	node = dal:GetNode(node)
	if not node then return false,'ECP1NF:'..actpath end
	return asynccall(user,dal,cb,node,fun,...)
end
function callact(user,dal,n,actname,...)
	local act=n:__get_act_define(actname)
	local actfun=act and act.act
	if actfun then
		if user and not n:__check_acl(user,actname) then
			return nil,'ENPF1:'..n.__id .. ':' .. actname
		end
		if act.async then return nil,"can't call a async fun" end
		if type(actfun)~='function' then
			actfun=assert(loadactfun(dal,n,actfun))
		end
		return true,actfun(user,dal,n,actname,...)
	end
	return nil,'ENADF1:'..n.__id .. ':' .. actname
end
function callactpath(user,dal,k,actpath,...)
	local node, fun = string.match(actpath, "^(.+)%.(.-)$")
	if not node or not fun then return false,'EBCP1:'..actpath end
	node = dal:GetNode(nodeid)
	if not node then return false,'ECP1NF:'..actpath end
	return callact(user,dal,node,fun,...)
end

function fetch_node(user,dal,n,ak,deep)
	if user and not n:__check_acl(user,'_fetch') then
		return {__err='ENPF1:'..n.__id .. ':_fetch'}
	end
	if type(ak)=='string' and string.byte(ak,1)==0x7b then
		ak=convval(ak)
		if type(ak)~='table' then return false,'EBAT1:'..type(ak) end
	end
	deep = deep or 0
	local ty=type(ak)
	if ty=='nil' then
		local rv=n:__get_fields_values({},nil,nil)
		if deep>0 then
			local allsub=n:__get_child_all()
			for subk,subv in pairs(allsub) do
				if rv[subk]==nil then
					rv[subk]=fetch_node(user,dal,subv,nil,deep-1)
				end
			end
		end
		return rv
	elseif ty=='table' then
		local emptytb={___='...'}
		local rv={}
		for k,v in pairs(ak) do
			if k=='`n' then
				
			else
				local ty2=type(v)
				if type(k)=='string' then
					if k:sub(1,2)=='`m' then
						local mp=k:sub(3)
						if ty2=='table' then
							local allsub=n:__get_child_all()
							for subk,subv in pairs(allsub) do
								if rv[subk]==nil and string.match(tostring(subk),mp) then
									if deep>0 then
										rv[subk]=fetch_node(user,dal,subv,v,deep-1)
									else
										rv[subk]=emptytb
									end
								end 
							end
						elseif ty2=='number' then
							local allsub=n:__get_child_all()
							for subk,subv in pairs(allsub) do
								if rv[subk]==nil and string.match(tostring(subk),mp) then
									rv[subk]=fetch_node(user,dal,subv,nil,v)
								end
							end
						elseif ty2=='string' then
							if #v==0 then
								n:__get_fields_values(rv,1,nil,mp)
							else
								local mvv=n:__get_fields_values(nil,1,nil,mp)
								for _k,_v in pairs(mvv) do
									rv[string.gsub(v,'%?',_k)]=v
								end
							end
						else
							--todo: no use on this condition
						end
					elseif k:sub(1,2)=='`w' then
						local mp=k:sub(3)
						if ty2=='table' then
							local allsub=n:__get_child_ex(string.match(mp,"(.*)`(.*)`(.*)"))
							for subk,subv in pairs(allsub) do
								if rv[subk]==nil then
									if deep>0 then
										rv[subk]=fetch_node(user,dal,subv,v,deep-1)
									else
										rv[subk]=emptytb
									end
								end 
							end
						elseif ty2=='number' then
							local allsub=n:__get_child_ex(string.match(mp,"(.*)`(.*)`(.*)"))
							for subk,subv in pairs(allsub) do
								if rv[subk]==nil then
									rv[subk]=fetch_node(user,dal,subv,nil,v)
								end
							end
						else
							--todo: no use on this condition
						end
					else
						local _fd=n:__get_field_define(k)
						if _fd then
							rv[(ty2=='string' and #v>0 and v) or k]=n[k]
						else
							_fd=n:__get_child_define(k)
							if _fd then
								if ty2=='table' then
									if deep>0 then
										rv[k]=fetch_node(user,dal,sv,v,deep-1)
									else
										rv[k]=emptytb
									end
								elseif ty2=='number' then
									rv[k]=fetch_node(user,dal,sv,nil,v)
								else
									if deep>0 then
										rv[k]=fetch_node(user,dal,sv,nil,deep-1)
									else
										rv[k]=emptytb
									end
								end
							end
						end
					end
				elseif type(k)=='number' then
					local _fd=n:__get_field_define(k)
					if _fd then
						rv[k]=sv
					else
						--skip not defined subs
					end
				else
					
				end
			end
		end
		if ak['`n'] then
			for k,v in pairs(rv) do
				if type(v)=='table' and not next(v) then
					rv[k]=nil
				end
			end
		end
		return rv
	else
		return {__err='EBAT1:'..ty .. ' for node field/id'}
	end
end

local createsub_sub
local delsub_sub
local function updatesub_sub(user,dal,_nfd,par,k,n,mc,dft,lvl)
	if user and not n:__check_acl(user,'_update') then
		return false,'ENPF1:update'
	end

	-- check the custom create on field
	if _nfd.update then
		return false,'EDBS1H:update'
	end
	
	local r,a
	
	if mc and mc~=n._cmc and not (mc=='' and n._cmc==nil) then
		-- replace old
		r,a=delsub_sub(user,dal,_nfd,par,k,n,lvl)
		if not r then return r,a end
		return createsub_sub(user,dal,_nfd,par,k,mc,dft,lvl)
	end	
	
	if dft then
		local of,fd,er=n:__get_ordered_fields(dft)
		if dft._pto or dft._fds or dft._dfs then
			fd=nil
			for k,v in ipairs(er) do
				table.insert(of,v)
			end
			er={}
		end
		
		for _,k1 in pairs(of) do
			local v=dft[k1]
			if k1~='_' then
				local _fd=(fd and fd[k1]) or n:__get_field_define(k1)
				if _fd then
					local ft=_fd[2] or _defaulttype
					local r,nv=ft:_validU(v,_fd[3])
					if not r then return r,'err when set '..k1..':'..nv end
					r,a=set_sub(user,dal,_fd,n,k1,nv,n[k1],lvl)
					if not r then return r,a end
				else
					if type(v)=='table' then
						table.insert(er,k1)
					else
						--warning for extra params
						print('warning for extra field params:',k1,v)
					end
				end
			end
		end
		for _,k1 in pairs(er) do
			if k1~='_' then
				local v=dft[k1]
				if type(v)=='table' then
					local cd,_fd=n:__get_child_define(k1)
					if cd then
						local org=n:__get_child(k1)
						if (org) then
							r,a=updatesub_sub(user,dal,_fd,n,k1,org,v._,v,(lvl or 0)+1)
							if not r then return r,a end
						else
							r,a=createsub_sub(user,dal,_fd,n,k1,v._,v,(lvl or 0)+1)
							if not r then return r,a end
						end
					else
						--warning for extra params
						print('warning for extra params:',k1)
					end
				else
					print('warning for extra field params:',k1,v)
				end
			end
		end
	end
	n:__save_ta()
	return n
end

function mergenilf(nilf)
	for k,v in ipairs(nilf) do
		if type(v)=='table' then
			nilf[k]=mergenilf(v)
		end
	end
	return '{'.. table.concat(nilf,' ') .. '}'
end

createsub_sub=function (user,dal,_fd,n,k,mc,dft,lvl)
	if user and not n:__check_acl(user,'_create') then
		return false,'ENPF1:create'
	end
	-- check the custom create on field
	if _fd.create~=nil then
		return predefcall(dal,n,_fd.create,user,dal,n,k,mc,dft,lvl)
	end

	if type(mc)=='table' then dft,mc=mc,dft end
	if type(mc)~='string' then mc=dft and dft._ end
	if type(mc)=='string' and string.byte(mc,1)==0x7b then
		dft=convval(mc)
		mc=dft and dft._
		if type(dft)~='table' then return false,'EBAT1:'..type(dft) end
	end

	--if n:__get_child(k) then return false, 'ESN1E:'..k end

	local sn,errmsg=n:__make_child(k,mc)
	if not sn then return false,errmsg or ('ENC1O2:'..k..'`'..n.__id) end
	
	--now do hook function
	local hka,hkta
	local hkb,hkt=_fd and _fd.hkb,n._hkt
	if hkb then
		r,sn,hka=hkb(user,dal,n,k,sn,nil)
		if not r then return r,sn or  'EDBBH' end
	end
	hkb=hkt and hkt.hkb
	if hkb then
		r,sn,hkta=hkb(user,dal,n,k,sn,nil)
		if not r then return r,sn or 'EDBBH' end
	end
	
	sn,errmsg=n:__add_child(k,mc,sn)
	if not sn then return false,errmsg or 'EDBAS' end
		
	if hka then hka(user,dal,n,k,sn,nil) end
	hka=_fd and _fd.hka
	if hka then hka(user,dal,n,k,sn,nil) end
	if hkta then hkta(user,dal,n,k,sn,nil) end
	hka=hkt and hkt.hka
	if hka then hka(user,dal,n,k,sn,nil) end
	
	-- merge dft to nv
	local r,s = updatesub_sub(user,dal,_fd,n,k,sn,nil,dft,(lvl or 0)+1)
	if not r then return r,s end
	
	-- check the nil value
	local nilf=sn:__check_nil_fields(false,user)
	if nilf then
		return false, 'ENFF1:'..mergenilf(nilf)
	end
	
	return sn
end
-----------------------
function createsub(user,dal,n,k,mc,dft,lvl)
	if user and not n:__check_acl(user,'_create') then
		return false,'ENPF1:create'
	end
	if n:__get_child(k) then return false, 'ESN1EO2:'..k..'`'..n.__id end
	local cd,_fd=n:__get_child_define( k)
	if not cd then return false,'ENSDF1E2:'..k..'`'..(_fd or '') end

	if type(mc)=='table' then dft,mc=mc,dft end	 
	if type(mc)~='string' then mc=dft and dft._ end
	if type(mc)=='string' and string.byte(mc,1)==0x7b then
		dft=convval(mc)
		mc=dft and dft._
		if type(dft)~='table' then return false,'EBAT1:'..type(dft) end
	end
	
	-- check the custom create on field
	if _fd.create~=nil then
		return predefcall(dal,n,_fd.create,user,dal,n,k,mc,dft,lvl)
	end
	
	return createsub_sub(user,dal,_fd,n,k,mc,dft,lvl)
end

function updatesub(user,dal,n,k,mc,dft,lvl)
	if user and not n:__check_acl(user,'_update') then
		return false,'ENPF1:update'
	end
	if type(mc)=='table' then dft,mc=mc,dft end	 
	if type(mc)~='string' then mc=dft and dft._ end
	if type(mc)=='string' and string.byte(mc,1)==0x7b then
		dft=convval(mc)
		mc=dft and dft._
		if type(dft)~='table' then return false,'EBAT1:'..type(dft) end
	end

	local cd,_fd=n:__get_child_define(k)
	if not cd then return false,'ENSDF1:'..k..'`'..(_fd or '') end

	--first check node's act for createsub
	if _fd.update~=nil then
		return predefcall(dal,n,_fd.update,user,dal,n,k,mc,dft,lvl)
	end

	local org=n:__get_child(k)
	if not org then
		--if not mc then return false,'EN1NE:' .. k end
		if _fd.create then
			return _fd.create(user,dal,n,k,mc,dft,lvl)
		end	
		return createsub_sub(user,dal,_fd,n,k,mc,dft,lvl)
	end
	
	return updatesub_sub(user,dal,_fd,n,k,org,mc,dft,lvl)
end

local function replacesub_sub(user,dal,_fd,n,k,mc,dft,lvl)
	if user and not n:__check_acl(user,'_replace') then
		return false,'ENPF1:replace'
	end

	-- check the custom create on field
	if _fd.replace~=nil then
		return predefcall(dal,n,_fd.replace,user,dal,n,k,mc,dft,lvl)
	end
	
	local r,a
	
	-- replace old
	r,a=delsub_sub(user,dal,_fd,n,k,n,lvl)
	if not r then return r,a end
	return createsub_sub(user,dal,_fd,n,k,mc,dft,lvl)
end

function replacesub(user,dal,n,k,mc,dft,lvl)
	if user and not n:__check_acl(user,'_replace') then
		return false,'ENPF1:replace'
	end
	if type(mc)=='table' then dft,mc=mc,dft end	 
	if type(mc)~='string' then mc=dft and dft._ end
	if type(mc)=='string' and string.byte(mc,1)==0x7b then
		dft=convval(mc)
		mc=dft and dft._
		if type(dft)~='table' then return false,'EBAT1:'..type(dft) end
	end

	local cd,_fd=n:__get_child_define(k)
	if not cd then return false,'ENSDF1:'..k..'`'..(_fd or '') end
	--first check node's act for createsub
	if _fd.replace~=nil then
		return predefcall(dal,n,_fd.replace,user,dal,n,k,mc,dft,lvl)
	end

	local org=n:__get_child(k)
	if not org then return false, 'ESN1NE:'..k end

	
	return replacesub_sub(user,dal,_fd,n,k,mc,dft,lvl)
end


delsub_sub=function(user,dal,_fd,n,k,org,lvl)
	if user and not n:__check_acl(user,'_del') then
		return false,'ENPF1:del'
	end

	-- check the custom del on field
	if _fd.del~=nil then
		return predefcall(dal,n,_fd.del,user,dal,n,k,lvl)
	end

	--now do hook function
	local hka,hkta
	local hkb,hkt=_fd and _fd.hkb,n._hkt
	if hkb then
		r,nv,hka=predefcall(dal,n,hkb,user,dal,n,k,nil,org,lvl)
		if not r then return r,nv or  'EDBBH' end
	end
	hkb=hkt and hkt.hkb
	if hkb then
		r,nv,hkta=predefcall(dal,n,hkb,user,dal,n,k,nil,org,lvl)
		if not r then return r,nv or 'EDBBH' end
	end

	local orghktad
	local orghkt=org._hkt
	if orghkt and orghkt.hkbd then
		r,nv,orghktad=predefcall(dal,org,orghkt.hkbd,user,dal,org,lvl)
		if not r then return r,nv or 'EDBDBH' end
	end

	local r,a
	local allcids=org:__get_child_key_list()
	for _,k1 in ipairs(allcids) do
		local cd,_fd=org:__get_child_define(k1)
		if cd then
			local sn=org:__get_child(k1)
			r,a = delsub_sub(user,dal,_fd,org,k1,sn,(lvl or 0)+1)
			if not r then return r,a end
		end
	end
	
	if org._rod then
		allcids=org:__get_other_fields(org:__get_ordered_fields_name_lvl())
		for idx=#allcids,1,-1 do
			local k1=allcids[idx]
			local _fd=org:__get_field_define(k1)
			if _fd and not _fd.ro and _fd.rod then
				if _fd.rod~='true' then
					predefcall(dal,org,_fd.rod,user,dal,org,k1,nil,org[k1],(lvl or 0)+1,true)
				else
					if _fd.proto then
						local sn=org[k1]
						local ft=_fd[2] or defaulttype
						local _vl,ov=ft:_validU(org:__get_pto_field(k1))
						assert(_vl)
						r,a=set_sub(user,dal,_fd,org,k1,ov,sn,(lvl or 0)+1,true)
						if not r then return r,a end
					elseif _fd.v then
						local sn=org[k1]
						local ft=_fd[2] or defaulttype
						local _vl,ov=ft:_validU(_fd.v)
						assert(_vl)
						r,a=set_sub(user,dal,_fd,org,k1,ov,sn,(lvl or 0)+1,true)
						if not r then return r,a end
					end
				end
			end
		end
	end
	if not (n._cfd and n._cfd[k]) then
		n:__remove_child(k,true)
		--org:__remove_ta(true)
	end
	if orghktad then orghktad(user,dal,org,lvl) end
	if orghkt and orghkt.hkad then
		r,nv,orghktad=predefcall(dal,org,orghkt.hkad,user,dal,org,lvl)
		if not r then return r,nv or 'EDBDBH' end
	end
	
	if hka then hka(user,dal,n,k,nil,org,lvl) end
	hka=_fd and _fd.hka
	if hka then predefcall(dal,n,hka,user,dal,n,k,nil,org,lvl) end
	if hkta then hkta(user,dal,n,k,nil,org,lvl) end
	hka=hkt and hkt.hka
	if hka then predefcall(dal,n,hka,user,dal,n,k,nil,org,lvl) end
	return true
end

function delsub(user,dal,n,k,lvl)
	if user and not n:__check_acl(user,'_del') then
		return false,'ENPF1:del'
	end
	local _cd,_cfd=n:__get_child_define(k)
	if not _cd then return false, 'ENSDF1:'..k..'`'..(_cfd or '') end
	if n._cfd and n._cfd[k] then return false, 'ESNNBU1:'..k end

	if _cfd.del~=nil then
		return predefcall(dal,n,_cfd.del,user,dal,n,k,lvl)
	end
	local org=n:__get_child(k)
	if not org then return true,'ESN1NE:'..k end
	
	return delsub_sub(user,dal,_cfd,n,k,org,lvl)
end
function mdelsub(user,dal,n,kt,lvl)
	local ty=type(kt)
	if ty=='string' then
		return delsub(user,dal,n,kt,lvl)
	elseif ty=='table' then
		if user and not n:__check_acl(user,'_del') then
			return false,'ENPF1:del'
		end
		for i,k in ipairs(kt) do
			local ty2=type(v)
			local r1,r2
			if type(k)=='string' and k:sub(1,2)=='`m' then
				local mp=k:sub(3)
				local allsub=n:__get_child_all()
				for subk,subv in pairs(allsub) do
					if string.match(subk,mp) then
						local _r1,_r2=delsub(nil,dal,n,subk,lvl)
						if not _r1 then return nil,_r2 end
					end 
				end
			else
				r1,r2=delsub(nil,dal,n,k,lvl)
				if not r1 then return nil,r2 end
			end
		end
		return true
	else
		return false,'EBAT1:'..ty
	end
end

function makenode(user,dal,id,data,ik,st)
	if user and not n:__check_acl(user,'_mk') then
		return false,'ENPF1:mk'
	end
	if dal:GetNode(id) then return false, 'ESN1E:'..id end
	local n,msg=dal:MakeNode(id,data,ik,st,true)
	if n then n:__save_ta() end
	return n,msg
end
function removenode(user,dal,id,deep)
	if user then
		local n=dal:GetNode(id)
		if not n or n:__check_acl(user,'_rm') then
			return false,'ENPF1:mk'
		end
	end
	dal:RemoveNodeTA(id,deep)
	return true
end
require'zdc.ds.module'
function clone(user,dal,n,toid,opt)
	if user and not n:__check_acl(user,'_clone') then
		return false,'ENPF1:clone'
	end
	local par,sid=zdc.idutil.GetParent(toid)
	if not par and user then
		return false,'bad arg toid'
	end
	if par then
		local pn=dal:GetNode(par)
		if pn then
			--if pn:__get_child(sid) then return false,'ESN1E:'..sid end
			local cd,_fd=pn:__get_child_define(sid)
			if not cd then return false,'ENSDF1:'..sid..'`'..(_fd or '') end
		
			-- check the custom create on field
			if _fd.create~=nil then return false,'dst not support clone' end
		end
	end
	opt=opt or {}
	return zdc.ds.module.CloneTo(dal,n.__id,toid,opt.includeroot,opt.filter_id_dpl,opt.force,opt.inspto,true)
end

local _metafdneed={
[1]=1,[3]=1,
proto=1,activate=1,
ro=1,vir=1,dyn=1,
_doc=2,
}
local function _fillonefd(tym,mtym,verb,fd,to)
	to=to or {}
	for k,v in pairs(fd) do
		local chk=_metafdneed[k]
		if chk and chk<=verb then
			to[k]=v
		end
	end
	local tn=(fd[2] or _defaulttype).name
	to[2]=tn
	if tym and not mtym[tn] then
		mtym[tn]=GetTypeMeta(tym,fd[2] or _defaulttype)
	end
	return to
end

local function _fillonecd(user,dal,m,tym,cdm,verb,cd,to)
	to=to or {}
	if type(cd)=='string' then
		to.pto=string.match(cd,"^.-_pto%s-=%s-['\"](.-)['\"]")
	else
		to.tmpl=cd.tmpl
		to._mc=cd._mc
		to.pto=string.match(cd[1],"^.-_pto%s-=%s-['\"](.-)['\"]")
	end
	if cdm and not m.cdm[to.pto] then
		local np=dal:GetNode(to.pto)
		if np then
			local nm={tym=m.tym,cdm=m.cdm}
			if type(cdm)=='table' then
				zdc.nodeapi.metainfo(user,dal,np,nm,cdm.opt,cdm.verb,cdm.tym,cdm.cdm)
			else
				zdc.nodeapi.metainfo(user,dal,np,nm,nil,cdm)
			end
			nm.tym=nil
			nm.cdm=nil
			m.cdm[to.pto]=nm
		end
	end
	return to
end

local _metaactneed={
env=1,async=1,_doc=2
}
local function _filloneact(verb,act,to)
	to=to or {}
	for k,v in pairs(act) do
		local chk=_metaactneed[k]
		if chk and chk <=verb then
			to[k]=v
		end
	end
	return to
end

local _metakeyfun={}
_metakeyfun.fds=function(user,dal,n,m,opt,verb,tym,cdm,fds)
	fds=fds or {}
	local _fds=n._fds
	local mtym=m.tym
	if _fds then
		for k,v in pairs(_fds) do
			if v[1]>=1 and not v.hide then
				fds[k]=_fillonefd(tym,mtym,verb,v)
			end
		end
	end
	return fds
end
_metakeyfun.dfs=function(user,dal,n,m,opt,verb,tym,cdm,dfs)
	dfs=dfs or {}
	local _dfs=n._dfs
	local mtym=m.tym
	if _dfs then
		for k,v in pairs (_dfs) do
			if k=='_array' then
				local v2=v[2]
				if v2[1]>=1 and not v2.hide then
					dfs._array={v[1],_fillonefd(tym,mtym,verb,v2)}
				end
			elseif k=='_patterns' then
				local pp={}
				for ii, vv in ipairs(v) do
					local pt=type(vv[1])
					if pt=='string' then
						pp[ii]={vv[1],vv[2],_fillonefd(tym,mtym,verb,vv[3])}
					elseif pt=='table' then
						pp[ii]={GetTypeMeta(0,vv[1]),vv[2],_fillonefd(tym,mtym,verb,vv[3])}
						if tym and not mtym[vv[1]] then
							mtym[vv[1].name]=GetTypeMeta(verb,vv[1])
						end
					end
				end
			else
				if v[1]>=1 and not v.hide then
					dfs[k]=_fillonefd(tym,mtym,verb,v)
				end
			end
		end
	end
	return dfs
end
_metakeyfun.cds=function(user,dal,n,m,opt,verb,tym,cdm,cds)
	cds=cds or {}
	local _cds=n._cds
	local mtym=m.tym
	local mcdm=m.cdm
	if _cds then
		for k,v in pairs (_cds) do
			if k=='_array' then
				cds._array={v[1],_fillonecd(user,dal,m,tym,cdm,verb,v2)}
			elseif k=='_patterns' then
				local pp={}
				for ii, vv in ipairs(v) do
					local pt=type(vv[1])
					if pt=='string' then
						pp[ii]={vv[1],vv[2],_fillonecd(user,dal,m,tym,cdm,verb,vv[3])}
					elseif pt=='table' then
						pp[ii]={GetTypeMeta(0,vv[1]),vv[2],_fillonecd(user,dal,m,tym,cdm,verb,vv[3])}
						if tym and not mtym[vv[1]] then
							mtym[vv[1].name]=GetTypeMeta(verb,vv[1])
						end
					end
				end
				cds._patterns=pp
			elseif k=='_any' then
				cds._any=_fillonecd(user,dal,m,tym,cdm,verb,v)
			else
				cds[k]=_fillonecd(user,dal,m,tym,cdm,verb,v)
			end
		end
	end
	return cds
end
_metakeyfun.cfd=function(user,dal,n,m,opt,verb,tym,cdm,cfd)
	cfd=cfd or {}
	local _cfd=n._cfd
	if _cfd then
		for k,v in pairs(_cfd) do
			cfd[k]=v
		end
	end
	return cfd
end
_metakeyfun.cad=function(user,dal,n,m,opt,verb,tym,cdm,cad)
	cad=cad or {}
	local _cad=n._cad
	if _cad then
		for k,v in pairs(_cad) do
			cad[k]=v
		end
	end
	return cad
end
_metakeyfun.act=function(user,dal,n,m,opt,verb,tym,cdm,act)
	act=act or {}
	local _act=n._act
	if _act then
		for k,v in pairs(_act) do
			act[k]=_filloneact(verb,v)
		end
	end
	return act
end
function metainfo(user,dal,n,m,opt,verb,tym,cdm)
	if user and not n:__check_acl(user,'_meta') then
		return false,'ENPF1:meta'
	end
	verb=tonumber(verb) or 1
	m=m or {}
	if tym then
		m.tym=m.tym or {}
	end
	if cdm then
		m.cdm=m.cdm or {}
	end
	if type(opt)=='table' then
		for k,v in pairs(opt) do
			local f=_metakeyfun[k]
			if f then
				m[k]=f(user,dal,n,m,opt,verb,tym,cdm,{},v)
			end
		end
	else
		for k,f in pairs(_metakeyfun) do
			m[k]=f(user,dal,n,m,opt,verb,tym,cdm,{})
		end
	end
	return m
end

